package top.wilsonlv.jaguar.cloud.websocket.handler;

import com.alibaba.fastjson.JSONObject;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
import top.wilsonlv.jaguar.cloud.websocket.config.WebsocketConfig;
import top.wilsonlv.jaguar.cloud.websocket.properties.WebsocketProperties;
import top.wilsonlv.jaguar.cloud.websocket.sdk.WebsocketConstant;
import top.wilsonlv.jaguar.cloud.websocket.sdk.dto.MessageReceiveTopic;
import top.wilsonlv.jaguar.cloud.websocket.sdk.dto.WebsocketConnection;
import top.wilsonlv.jaguar.cloud.websocket.sdk.dto.WebsocketMessage;
import top.wilsonlv.jaguar.cloud.websocket.wrapper.WebsocketSessionWrapper;
import top.wilsonlv.jaguar.commons.web.util.WebUtil;
import top.wilsonlv.jaguar.security.model.SecurityUser;

import javax.annotation.Resource;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import static top.wilsonlv.jaguar.cloud.websocket.sdk.WebsocketConstant.*;

/**
 * @author lvws
 * @since 2021/6/22
 */
@Slf4j
@Component
public class WebsocketOnlineHandler extends AbstractWebSocketHandler {

    private static final Map<String, WebsocketSessionWrapper> WS_SESSION_ID_MAPPING = new ConcurrentHashMap<>();
    @Resource
    private JmsTemplate jmsTopicTemplate;
    @Resource
    private WebsocketProperties websocketProperties;
    @Resource
    private RedisTemplate<String, Serializable> redisTemplate;

    public static Map<String, WebsocketSessionWrapper> getWsSessionIdMapping() {
        return WS_SESSION_ID_MAPPING;
    }

    public static WebsocketSessionWrapper getSession(String sessionId) {
        return getWsSessionIdMapping().get(sessionId);
    }

    public void saveConnection(WebsocketSessionWrapper session, WebsocketMessage message) {
        LocalDateTime now = LocalDateTime.now();
        int expire = websocketProperties.getHeartBeatRate() + websocketProperties.getExpireTime();

        WebsocketConnection websocketConnection = new WebsocketConnection();
        websocketConnection.setSessionId(session.getId());
        websocketConnection.setAccessToken(session.getAccessToken());
        websocketConnection.setServerInstanceId(WebsocketConfig.SERVER_INSTANCE_ID);
        websocketConnection.setExpireTime(now.plusSeconds(expire));
        websocketConnection.setMessage(message);

        BoundHashOperations<String, Object, Object> operations = this.redisTemplate.boundHashOps(session.getOnlineKey());
        operations.put(session.getId(), websocketConnection);
        operations.expire(expire, TimeUnit.SECONDS);

        session.setLastHeartBeatTime(now);
        getWsSessionIdMapping().put(session.getId(), session);
    }

    @Override
    public void afterConnectionEstablished(@NonNull WebSocketSession webSocketSession) {
        WebsocketSessionWrapper session = new WebsocketSessionWrapper(webSocketSession);
        this.saveConnection(session, null);

        SecurityUser user = session.getUser();
        MessageReceiveTopic topic = new MessageReceiveTopic(user.getId(), user.getUserType(), session.getId(), WebsocketConfig.SERVER_INSTANCE_ID);
        String clientIp = Objects.requireNonNull(webSocketSession.getRemoteAddress()).getAddress().getHostAddress();
        topic.setMessage(new WebsocketMessage().setParam(WebUtil.CLIENT_IP, clientIp));

        jmsTopicTemplate.send(DESTINATION_WEBSOCKET_CONNECTED, mqSession -> mqSession.createObjectMessage(topic));

        if (log.isDebugEnabled()) {
            log.debug("establish websocket session : {}, username : {}", session.getId(), session.getUser().getUsername());
        }
    }

    @Override
    protected void handleTextMessage(@NonNull WebSocketSession webSocketSession, @NonNull TextMessage message) {
        log.debug("收到消息:{}", message.getPayload());
        WebsocketSessionWrapper session = getSession(webSocketSession.getId());

        WebsocketMessage websocketMessage = JSONObject.parseObject(message.getPayload(), WebsocketMessage.class);
        if (!StringUtils.hasText(websocketMessage.getType())) {
            return;
        }

        if (WebsocketConstant.MESSAGE_TYPE_PING.equals(websocketMessage.getType())) {
            saveConnection(session, websocketMessage);
            session.pong();
            return;
        }

        SecurityUser user = session.getUser();
        MessageReceiveTopic topic = new MessageReceiveTopic(user.getId(), user.getUserType(), session.getId(), WebsocketConfig.SERVER_INSTANCE_ID, websocketMessage);
        jmsTopicTemplate.send(DESTINATION_WEBSOCKET_RECEIVE_PREFIX + websocketMessage.getType(), mqSession -> mqSession.createObjectMessage(topic));
    }

    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession, @NonNull CloseStatus status) {
        WebsocketSessionWrapper session = getWsSessionIdMapping().get(webSocketSession.getId());
        if (session == null) {
            return;
        }

        SecurityUser sessionUser = session.getUser();

        String onlineKey = String.format(WebsocketConstant.ONLINE_KEY_FORMAT, sessionUser.getId());
        this.redisTemplate.boundHashOps(onlineKey).delete(session.getId());
        getWsSessionIdMapping().remove(session.getId());

        if (log.isDebugEnabled()) {
            log.debug("close websocket session : {}, username : {}, status : {}", session.getId(), sessionUser.getUsername(), status.getCode());
        }

        SecurityUser user = session.getUser();
        MessageReceiveTopic topic = new MessageReceiveTopic(user.getId(), user.getUserType(), session.getId(), WebsocketConfig.SERVER_INSTANCE_ID);
        jmsTopicTemplate.send(DESTINATION_WEBSOCKET_CLOSE, mqSession -> mqSession.createObjectMessage(topic));
    }

    @Override
    public void handleTransportError(@NonNull WebSocketSession session, @NonNull Throwable exception) {
        log.debug(exception.getMessage());
    }

}
